上週學習完了基本工具的使用,今天開始我們要嘗試用梯度下降法來解決問題!
阿鐵想要買一輛新車,但是想知道這台車的耗油程度如何,除了看看車商數據(拿來廣告用的理想數據)之外,阿鐵也想從不同的車主身上實際去了解這台車的耗油程度,於是阿鐵就在論壇上發文詢問,很幸運的有 10 位好心車主分享了他們的油耗與里程數,資料如下
油耗(公升) | 里程(KM) |
---|---|
9 | 45 |
4 | 24 |
7 | 59 |
3 | 28 |
10 | 91 |
7 | 28 |
4 | 42 |
3 | 18 |
1 | 14 |
9 | 82 |
要如何找到「油耗」與「里程」之間的對應關係,來幫助阿鐵預測未來每公升的油能跑的公里數呢?這時候我們就用迴歸分析來解決這個問題!
這裡我們選擇最簡單也最常見的模型,就是線性模型
y = a * x + b
在圖上要畫出一條線有很多種可能,那麼我們要如何找到最好的那條線呢?換句話說,我們要如何找到一組 a & b,可以讓預測值與實際值之間的差距最小呢?
要找到那條線,也就是說,找到讓損失函數 (Loss Function)
變得最小的一組 a & b
沒錯,這裡我們就是會用到梯度下降法。
這裡複習一下,在梯度下降法中,我們會利用座標上某一點的梯度,來決定我們下一步要前進的方向。在這個例子當中,有兩個變數 a
和 b
,要計算梯度,需要分別計算 Loss function 對 a 和 b 的偏微分。
另外,我們需要設定一個起始點(一個開始走下山的點),以及一個 learning rate 來調整每一步跨出的步伐。
接下來,讓我們正式開始實作吧
1. 引入 NumPy 與 Matplotlib
import matplotlib.pyplot as plt
import numpy as np
2. 建立數值陣列
x_data = [ 9, 4, 7 , 3 , 10 , 7 , 4 , 3 , 1, 9]
y_data = [ 45 , 24 , 59 , 28 , 91 , 28 , 42 , 18 , 14, 82]
這裡的 x_data 就是油耗,y_data 就是里程數。同時,我們這裡假設模型為
# 假設模型為 y_data = a * x_data + b
3. 建立網格資料
這裡我們分別建立兩個一維等差陣列 X
與 Y
,其範圍是 0 到 10,每隔 0.05 產生一個值。
接著,我們建立了一個二維陣列 Z
,來儲存每一個 X[n], Y[n] 位置上的損失函數。根據先前提過的損失函數算法,寫法為
(y_data[n] - b - a*x_data[n])**2
# 建立網格資料
x = np.arange(0,10,0.05)
y = np.arange(0,10,0.05)
Z = np.zeros((len(x), len(y)))
X, Y = np.meshgrid(x, y)
for i in range(len(x)):
for j in range(len(y)):
b = x[i]
a = y[j]
Z[j][i] = 0
for n in range(len(x_data)):
Z[j][i] = Z[j][i] + (y_data[n] - b - a*x_data[n])**2
Z[j][i] = Z[j][i]/len(x_data)
4. 決定參數
這裡我們決定了 a 與 b 的起始點(從哪裡開始走),learning rate(每一步走多遠),iteration 次數(我們要走多少步),以及最後我們用兩個陣列,來分別儲存每一步我的走到的位置
# 決定 a 與 b 的起始點
b = 0
a = 0
# 決定 learning rate
lr = 0.00005
# 決定 iteration 的次數
iteration = 10000
# 儲存每一次 iterate 後的結果
b_history = [b]
a_history = [a]
5. 執行梯度下降
# 執行梯度下降
for i in range(iteration):
b_grad = 0.0
a_grad = 0.0
# 計算損失函數分別對 a 和 b 的偏微分
for n in range(len(x_data)):
b_grad = b_grad - 2.0*(y_data[n] - b - a*x_data[n])*1.0
a_grad = a_grad - 2.0*(y_data[n] - b - a*x_data[n])*x_data[n]
# 更新 a, b 位置
b = b - lr * b_grad
a = a - lr * a_grad
# 紀錄 a, b 的位置
b_history.append(b)
a_history.append(a)
6. 繪圖
# 建立等高線圖
plt.contourf(x,y,Z, 50, alpha=0.5, cmap=plt.get_cmap('jet'))
# 繪製目標點
plt.plot([1.97], [7.22], 'x', ms=12, markeredgewidth=3, color='red')
# 繪製 a,b iteration 的結果
plt.plot(b_history, a_history, 'o-', ms=3, lw=1.5, color='black')
# 定義圖形範圍
plt.xlim(0,5)
plt.ylim(0,10)
# 繪製 x 軸與 y 軸的標籤
plt.xlabel(r'$b$', fontsize=16)
plt.ylabel(r'$a$', fontsize=16)
plt.show()
這裡你可能會有個問題,我們還沒開始走,怎麼就知道目標點在哪裡呢?這個讓我明天再來說明
7. 結果出爐
圖形上的每個黑點,都代表著我們的每一步。我們看到這些黑點從 b = 0 & a = 0(也就是圖形的原點)開始向 1 點鐘方向前進。根據我們對等高線的理解,這樣子切過一條又一條的等高線,不是在上升就是在下降。
當然這裡我們是不斷的下降高度。這裡我加上等高線的值,讓你可以更清楚的了解。
然而走到某一個階段之後,有個轉彎,往另外一個方向前進。雖然圖形上不是很明顯,但實際上我們仍然不斷的下降當中。
那麼,最後我們走到哪裡呢?這裡讓我們印出走了一萬步之後,最後一步的位置
(看起來其實距離目標點 1.97, 7.22 很接近了,但還是不夠接近呢)
也就是說,機器走到目前為止,認為 y = 7.2328846321328095 * x + 1.8477027996627977
是一條最好的線,能夠最好的「預測」油耗與里程之間的關係。這就是機器學習如何預測的過程!
今天很快的示範了如何用 Python 來實作梯度下降法,但是過程中似乎還有許多問題需要討論,像是
... 等等的問題。讓我們用後續幾天的時間,來慢慢理解更多關於梯度下降法的事情!